<?php


namespace wanghua\general_utility_tools_php\tool;

use think\Db;
use think\facade\Request;
use wanghua\general_utility_tools_php\Date;
use wanghua\general_utility_tools_php\phpmailer\Exception;

/**
 * 系统通用工具
 *
 * 环境要求：
 *     ThinkPHP5.1+,PHP7.0+
 *
 * Class Tools
 * @package libraries
 */
class Tools
{

    /**
     * 获取当前系统时间(精确到毫秒)
     * @return float
     */
    static function getMillisecond()
    {
        list($t1, $t2) = explode(' ', microtime());
        //return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
    }

    /**
     * 获取当前系统时间(精确到微秒)
     * @return float
     */
    static function getMicrosecond()
    {
        list($t1, $t2) = explode(' ', microtime());
        //return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000000);
        return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000000);
    }

    /**
     * desc：返回当前毫秒时间戳的日期格式
     * author：wh
     * return 2021-08-10 16:11:10 128
     */
    static function getMillisecondNowTime(){
        list($t1, $t2) = explode(' ', microtime());
        return date('Y-m-d H:i:s',$t2).' '.sprintf('%.0f',$t1*1000);
    }

    /**
     * desc：一般性通用返回结果封装
     * 注：本方法仅适用于thinkPHP5.0
     * author：wh
     * @param $code
     * @param string $message 错误消息
     * @param array $data 返回数据
     * @param bool $log 是否写入运行时日志(runtime/log/*)
     * @param bool $json 返回json格式
     * @return array
     */
    static function set_res($code, $message = '', $data = [], $log = false, $json = false,$action='')
    {
        $r = [
            'code' => $code,
            'msg' => $message,
            'data' => $data,
        ];

        if (is_array($code)) {
            $r = array_merge($code, $r);
        }

        if ($log) {
            $dirname = 'result_log';
            if (is_array($log)) {
                $dirname = isset($log['dirname']) ? $log['dirname'] : $dirname;//存储在runtime/log/下面
            }
            $root_path = explode('vendor',__DIR__)[0];
            $filepath = $root_path . 'runtime/log/' . ($dirname). '/'.date('Ymd');//运行时日志
            if (!file_exists($filepath)) {
                mkdir($filepath, 0777, true);
            }

            $filepath .= '/log' . date('YmdH') . '.txt';
            $str = "\n" . date('Y-m-d H:i:s') . ' | ' . request()->baseUrl() . "\n";

            $str .= 'PARAMS: ';//参数
            $str .= json_encode(input(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";

            $str .= 'RESULT: ';//结果
            $str .= json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ' | ';

            file_put_contents($filepath, $str, FILE_APPEND);
        }
        return $json ? json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $r;
    }

    /**
     * desc：操作失败
     * author：wh
     * @param string $msg 错误信息
     * @param array $data 返回数据
     * @param string $action 操作标识、控制器具体操作方法
     * @return array
     */
    static function set_ok($msg='ok',$data=[],$action=''){
        if(is_array($msg)){
            $data = $msg;
            $msg = 'ok';
        }
        return ['code'=>200,'msg'=>$msg,'action'=>$action,'data'=>$data];
    }

    /**
     * desc：操作失败
     *
     * author：wh
     * @param string $msg 错误信息
     * @param array $data 返回数据
     * @param string $action 操作标识、控制器具体操作方法
     * @return array
     */
    static function set_fail($msg='操作失败',$data=[],$action=''){
        return ['code'=>500,'msg'=>$msg,'action'=>$action,'data'=>$data];
    }
    /**
     * desc：一般性通用返回结果封装
     *
     * 注：仅适用于thinkPHP5.1+
     *
     * author：wh
     * @param array|int $code 错误码 一般为数字（可以是数组）
     *
     * @param string $message 错误消息
     *
     * @param array $data 返回数据
     *
     * @param bool $log 是否写入运行时日志(runtime/log/*)
     *
     * @param bool $json 返回json格式
     *
     * @return array
     */
    static function setres($code, $message = '', $data = [], $log = false, $json = false)
    {
        $r = [
            'code' => $code,
            'msg' => $message,
            'data' => $data,
        ];

        if (is_array($code)) {
            $r = array_merge($code, $r);
        }

        if ($log) {
            $dirname = 'result_log';//存储在runtime/log/下面
            if (is_array($log)) {
                $dirname = isset($log['dirname']) ? $log['dirname'] : $dirname;
            }

            $filepath = \think\facade\App::getRuntimePath() . 'log/' . ($dirname). '/'.date('Ymd');//运行时日志
            if (!file_exists($filepath)) {
                mkdir($filepath, 0777, true);
            }

            $filepath .= '/log' . date('YmdH') . '.txt';
            $str = "\n" . date('Y-m-d H:i:s') . ' | ' . request()->baseUrl() . "\n";

            $str .= 'PARAMS: ';//参数
            $str .= json_encode(input(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";

            $str .= 'RESULT: ';//结果
            $str .= json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";


            file_put_contents($filepath, $str, FILE_APPEND);
        }
        return $json ? json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $r;
    }

    /**
     * desc：封装json格式的结果
     * author：wh
     * @param $code
     * @param string $message
     * @param array $data
     * @return false|string
     */
    static function json_res($code, string $message = '', $data = [])
    {
        $r = [
            'code' => $code,
            'msg' => $message,
            'data' => $data,
        ];

        if (is_array($code)) {
            $r = array_merge($code, $r);
        }

        return json_encode($r, JSON_UNESCAPED_UNICODE);
    }
    static function json_ok(string $message = 'ok', $data = [])
    {
        $r = [
            'code' => 200,
            'msg' => $message,
            'data' => $data,
        ];
        return json_encode($r, JSON_UNESCAPED_UNICODE);
    }
    static function json_fail(string $message = 'fail', $data = [])
    {
        $r = [
            'code' => 500,
            'msg' => $message,
            'data' => $data,
        ];
        return json_encode($r, JSON_UNESCAPED_UNICODE);
    }
    /**
     * desc：socket专用json格式
     *
     * author：wh
     * @param $action
     * @param string $msg
     * @param array $items
     * @param string $method
     * @return false|string
     */
    static function json_wss($action,$msg='', $items=[], $method='response'){
        $json = [
            'action'=>$action,
            'method'=>$method,
            'msg'=>$msg,
            'items'=>$items
        ];
        return json_encode($json, JSON_UNESCAPED_UNICODE);
    }
    /**
     * desc：获取框架版本[兼容各个ThinkPHP框架]
     *
     * 注：要求框架5.0及以上
     *
     * author：wh
     *
     * @param string $prefix_version 框架版本号前缀 eg:thinkPHP5.0就填写5，thinkPHP5.1就填写5.1，thinkPHP6.0就填写6
     *
     * @return mixed
     *
     * 示例代码 eg：
     *
     * $version = Tools::think_version(5);
     *
     * dump($version);
     */
    static function think_version(string $prefix_version)
    {
        //框架各个版本获取方式
        if ($prefix_version == '5') {//thinkPHP5.0
            return THINK_VERSION;
        } else{
            //thinkPHP5.1+
            return \think\facade\App::version();
        }
    }


    /**
     * desc：获取框架运行时目录[兼容各个ThinkPHP框架]
     *
     * 注：要求框架5.0及以上
     *
     * author：wh
     *
     *
     * @param string $prefix_version 框架版本号前缀 eg:thinkPHP5.0就填写5，thinkPHP5.1就填写5.1，thinkPHP6.0就填写6
     *
     *
     * @return mixed
     *
     * 示例代码 eg：
     *
     * $version = Tools::think_version(5);
     *
     * dump($version);
     */
    static function think_runtime_dir(string $prefix_version)
    {
        //各个版本框架获取运行目录方式
        if ($prefix_version == '5') {//thinkPHP5.0
            return RUNTIME_PATH;
        } else {//thinkPHP5.1+
            return \think\facade\App::version();
        }
    }

    /**
     * UTF8字符串加密
     * @param string $string
     * @return string
     */
    static function encode($string)
    {
        list($chars, $length) = ['', strlen($string = iconv('utf-8', 'gbk', $string))];
        for ($i = 0; $i < $length; $i++) {
            $chars .= str_pad(base_convert(ord($string[$i]), 10, 36), 2, 0, 0);
        }
        return $chars;
    }

    /**
     * UTF8字符串解密
     * @param string $string
     * @return string
     */
    static function decode($string)
    {
        $chars = '';
        foreach (str_split($string, 2) as $char) {
            $chars .= chr(intval(base_convert($char, 36, 10)));
        }
        return iconv('gbk', 'utf-8', $chars);
    }

    /**
     * desc：换行输出（一般用于调试 eg：在循环中输出）
     * author：wh
     */
    static function br_echo($msg)
    {
        echo '<br/>';
        echo $msg;
        echo '<br/>';
    }


    /**
     * 数组转xml字符串
     * @throws
     **/
    static function to_xml($data)
    {
        if (!is_array($data) || count($data) <= 0) {
            throw new \Exception("数组数据异常！");
        }

        $xml = "<xml>";
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }
    /**
     * desc：xml转换为数组
     * author：wh
     * @param $xml
     * @return mixed
     */
    static function xml_to_array($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $values;
    }
    /**
     * desc：返回xml 对象
     * author：wh
     * @param bool $res
     * @return string
     */
    static function xml_res_return($res = true)
    {
        $tmp_suc = $res ? 'SUCCESS' : 'FAIL';
        $tmp_ok = $res ? 'OK' : 'ERR';
        $xml = "<xml>
              <return_code><![CDATA[{$tmp_suc}]]></return_code>
              <return_msg><![CDATA[{$tmp_ok}]]></return_msg>
            </xml>";

        return simplexml_load_string($xml);
    }

    /**
     * XML编码
     * @param mixed $data 数据
     * @param string $encoding 数据编码
     * @param string $root 根节点名
     * @return string
     */
    static function xml_encode($data, $encoding = 'utf-8', $root = 'xml')
    {
        $xml = '<?xml version="1.0" encoding="' . $encoding . '"?>';
        $xml .= '<' . $root . '>';
        $xml .= self::data_to_xml($data);
        $xml .= '</' . $root . '>';
        return $xml;
    }

    /**
     * 数据XML编码
     * @param mixed $data 数据
     * @return string
     */
    static function data_to_xml($data)
    {
        $xml = '';
        foreach ($data as $key => $val) {
            is_numeric($key) && $key = "item id=\"$key\"";
            $xml .= "<$key>";
            $xml .= (is_array($val) || is_object($val)) ? self::data_to_xml($val) : $val;
            list($key,) = explode(' ', $key);
            $xml .= "</$key>";
        }
        return $xml;
    }

    /**
     * 生成随机字符串
     * @param int $length 生成长度
     * @param int $type 生成类型：0-小写字母+数字，1-小写字母，2-大写字母，
     * 3-数字，4-小写+大写字母，5-小写+大写+数字
     * @return string
     */
    static function rand_str($length = 8, $type = 0)
    {
        $a = 'abcdefghijklmnopqrstuvwxyz';
        $A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $n = '0123456789';

        switch ($type) {
            case 1:
                $chars = $a;
                break;
            case 2:
                $chars = $A;
                break;
            case 3:
                $chars = $n;
                break;
            case 4:
                $chars = $a . $A;
                break;
            case 5:
                $chars = $a . $A . $n;
                break;
            default:
                $chars = $a . $n;
        }

        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[mt_rand(0, strlen($chars) - 1)];
        }
        return $str;
    }

    /**
     * desc：php二维数组排序(升、降)
     * author：wh
     * @param $data 数据源(必须是二维数组)
     * @param $field 要排序的字段(必须) eg：年龄或者价格
     * @param bool $sort 排序方式
     * @param bool $unique_field 指定唯一字段 eg：例如userID一般都是唯一的
     * @return array 返回排序后的数据源
     */
    static function arr_arr_to_sort($data, $field, $sort = true, $unique_field)
    {
        //取出排序源
        $field_arr_key = array_column($data, $unique_field);
        $field_arr_val = array_column($data, $field);

        $source_arr = [];
        foreach ($field_arr_key as $key => $val) {
            $source_arr[$val] = $field_arr_val[$key];
        }

        //排序
        if ($sort) arsort($source_arr);
        else asort($source_arr);
        //重组数据
        $new_arr = [];
        foreach ($source_arr as $k => $v) {
            foreach ($data as $a => $b) {
                if ($k == $b[$unique_field]) {
                    array_push($new_arr, $b);
                }
            }
        }
        return $new_arr;
    }

    /**
     * desc：日期换算为 今天 昨天 2天前 一周前 一个月前 一年前
     * author：wh
     * @param $date 时间戳
     */
    static function date_revert_to_string_type($date)
    {
        $date = $date * 1;
        $arr = [
            0 => '今天',
            1 => '昨天',
            2 => '前天',
            7 => '一周前',
            30 => '一个月前',
            365 => '一年前',
            -1 => '很久以前',
        ];
        //今天
        $today = strtotime(date('Y-m-d'));
        if (($date - $today) >= 0) {
            return $arr[0];
        } else if (($date - $today) < 0 && ($today - $date) <= 86400) {
            return $arr[1];
        } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 2) {
            return $arr[2];
        } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 7) {
            return $arr[7];
        } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 30) {
            return $arr[30];
        } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 365) {
            return $arr[365];
        }
        return $arr[-1];
    }

    /**
     * desc：返回由二维数组的其中两个字段(键)组成的一维数组
     * author：wh
     */
    static function key_val_arr($data, $key, $key2)
    {
        $arr = array_column($data, $key);
        $arr2 = array_column($data, $key2);
        //$tmp = [];
        //foreach ($arr as $k => $v) {
        //    $tmp[$v] = $arr2[$k];
        //}
        //修改为-更加高效（数组1和数组2的长度必须一致!）
        return array_combine($arr,$arr2);
    }

    /**
     * desc：查找二维数组中某个字段值为val的数据，并返回这条数据（一维数组）
     * author：wh
     */
    static function arr_data_one($data, $field, $val)
    {
        foreach ($data as $k => $v) {
            if ($v[$field] == $val) {
                return $v;
            }
        }
        return false;
    }

    /**
     * description：unicode编码
     * author：wh
     * @param $str
     * @return string
     */
    static function unicodeEncode($str)
    {
        //split word
        preg_match_all('/./u', $str, $matches);

        $unicodeStr = "";
        foreach ($matches[0] as $m) {
            //拼接
            $unicodeStr .= "&#" . base_convert(bin2hex(iconv('UTF-8', "UCS-4", $m)), 16, 10);
        }
        return $unicodeStr;
    }

    /**
     * desc：返回unicode解码后的中文
     *   $name = '\u65b0\u6d6a\u5fae\u535a';
     *   $data = unicodeDecode($name); //输出新浪微博
     * 配合 unicodeDecode 使用
     * author：wh
     * @param $match
     * @return string
     */
    static function replace_unicode_escape_sequence($match)
    {
        return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
    }

    /**
     * desc：中文被unicode编码后了的数据，解码出中文
     * 使用Unicode解码
     * author：wh
     * @param $data
     * @return null|string|string[]
     */
    static function unicodeDecode($data)
    {
        return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $data);
    }

    /**
     * @deprecated 废弃
     *
     * desc：返回11位数编号，末位一位表示年份,第4位开始3位数表示几月几日
     * eg：28431855779；9=2019，318=11月15日
     * 注意：随机数都可能会重复
     * author：wh
     * @return string
     * @throws \Exception
     */
    static function create_number()
    {
        $day = date('z', mktime(0, 0, 0, date('m'), date('d'), date('Y')));
        $day = sprintf('%03d', $day);
        $sp = str_split(date('y'));

        $rand = random_int(1000000, 9999999);
        $str1 = substr($rand, 0, 3);
        $str2 = substr($rand, 3, strlen($rand));

        return ($str1 . $day . $str2 . $sp[1]);
    }

    /**
     * @deprecated 废弃
     *
     * [create_batch 生成交易批次号]
     * @Author
     * @Date   2019-12-26
     * @param  [type]     $merchant_basic_id [商户id]
     * @param  [type]     $last_batch        [最新的批次号]
     * @return [type]                        [description]
     */
    static function create_batch($merchant_basic_id, $last_batch)
    {
        $basic_no = str_pad($merchant_basic_id, 4, '0', STR_PAD_LEFT);
        if (empty($last_batch) || substr($last_batch, 0, 8) != date('Ymd')) {
            $batch = date('Ymd') . '-' . $basic_no . '01';
        } else {
            $batch_no = (int)substr($last_batch, -2); //批次号 末两位字符串数字转换为整数
            $batch = date('Ymd') . '-' . $basic_no . str_pad(($batch_no + 1), 2, '0', STR_PAD_LEFT);
        }
        return $batch;
    }

    /**
     * @deprecated 废弃
     *
     * [create_serial_number 生成流水号]
     * @Author
     * @Date   2019-11-27
     * @return [type]     [description]
     */
    static function create_serial_number()
    {
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $micro_second = substr($microtime, -3);
        $rand_str = self::rand_str(6, 3);
        return date('YmdHis') . $micro_second . $rand_str;
    }

    /**
     * @deprecated 废弃
     *
     * [create_order_number 生成单据编号 22位]
     * @Author wh
     * @Date   2019-11-27
     * @return [type]     [description]
     */
    static function create_order_number()
    {
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $micro_second = substr($microtime, -3);
        $rand_str = self::rand_str(8, 3);
        return time() . $micro_second . $rand_str;
    }

    /**
     * @deprecated 废弃
     *
     * [create_order_number 生成单据编号 25位]
     * @Author wh
     * @Date   2019-11-27
     * @return [type]     [description]
     */
    static function create_order_no()
    {
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $micro_second = substr($microtime, -3);
        $rand_str = self::rand_str(8, 3);
        return date('YmdHis') . $micro_second . $rand_str;
    }

    /**
     * @deprecated 废弃
     *
     * [create_invoice_apply_number 生成发票申请编号]
     * @Author
     * @Date   2019-12-23
     * @return [type]     [description]
     */
    static function create_invoice_apply_number()
    {
        list($t1, $t2) = explode(' ', microtime());
        $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
        $rand_str = self::rand_str(4, 3);
        return $microtime . $rand_str;
    }

    /**
     * desc：获取当前时间
     * author：wh
     * @return false|string
     */
    static function get_now_date()
    {
        return date('Y-m-d H:i:s');
    }

    /**
     * desc：获取格林威治初始时间，字符串格式
     * author：wh
     * @return string
     */
    static function get_init_time()
    {
        return '1970-01-01 10:00:00';
    }

    /**
     * desc：将时间戳转换为字符串的日期格式
     *
     * author：wh
     * @param int $time eg：1634118487(精确到秒)
     * @param string $format eg: Y-m-d 、 Y-m-d H:i:s等
     * @return false|string eg: 2021-10-13 17:48:07
     */
    static function time_to_date(int $time, string $format='Y-m-d H:i:s'){
        return date($format, $time);
    }

    /**
     * @deprecated 参考http/Curl.php
     *
     * cURL 网络链接库
     * GET
     * author：wh
     * @param $url
     * @return bool|int|string
     */
    static function curl_get($url, $timeout = 60)
    {

        $header = array(
            'Accept: application/json',
        );
        $curl = curl_init();
        //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        //curl_setopt($curl, CURLOPT_SSLVERSION, 3);
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        // 超时设置,以秒为单位
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //print "Error: ".curl_errno($curl).'-' . curl_error($curl);
            //返回错误码
            return ['code' => curl_errno($curl), 'msg' => curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code' => 200, 'msg' => 'ok', 'data' => $data];
        }
    }

    /**
     * @deprecated 参考http/Curl.php
     *
     * cURL 网络链接库[表单]
     * POST
     * author：wh
     * @param $url 是请求的链接
     * @param $postdata 传输的数据，数组格式
     * @return bool|int|string
     */
    static function curl_to_post($url, $postdata)
    {

        $timeout = 3;
        $connect_timeout = 2;
        $set_time_limit = 5;
        if ($timeout + $connect_timeout < $set_time_limit) throw new \Exception('脚本超时值必须大于等于连接超时与请求处理超时之和');
        set_time_limit($set_time_limit);
        $header = array(
            'Accept: application/json',
        );

        //初始化
        $curl = curl_init();
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        // 超时设置
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
        //发起连接前等待的时间，如果设置为0，则无限等待。
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connect_timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);

        //设置post方式提交
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($postdata));
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //返回错误码
            return ['code' => curl_errno($curl), 'msg' => curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code' => 200, 'msg' => 'ok', 'data' => $data];
        }
    }

    /**
     * @deprecated 参考http/Curl.php
     *
     * cURL 网络链接库[json]
     * POST
     * 特别注意：有些情况需要将$postdata参数用json_encode()函数处理一下
     * author：wh
     * @param $url 是请求的链接
     * @param $postdata 传输的数据，数组格式
     * @return bool|int|string
     */
    static function curl_post($url, $postdata, $timeout = 60)
    {

        $header = array(
            'Accept: application/json',
        );

        //初始化
        $curl = curl_init();
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        // 超时设置
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);

        //设置post方式提交
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //返回错误码
            return ['code' => curl_errno($curl), 'msg' => curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code' => 200, 'msg' => 'ok', 'data' => $data];
        }
    }

    /**
     * @deprecated 参考http/Curl.php
     *
     * cURL 网络链接库[流]
     * [请求Java接口]
     * 思考：1、Java端如果用文件流的方式去获取数据，调用此方法
     * @param $url
     * @param $postdata
     * @param int $timeout
     * @return array
     * @link
     * @example
     * @see
     */
    static function java_curl_post_file_request($url, $postdata, $timeout = 60)
    {

        $header = array(
            'Accept: application/json',
        );

        //初始化
        $curl = curl_init();
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        // 超时设置
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);

        //设置post方式提交
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //返回错误码
            return ['code' => curl_errno($curl), 'msg' => curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code' => 200, 'msg' => 'ok', 'data' => $data];
        }
    }

    /**
     * desc：将字符串按大写字母分割为数组
     * author：wh
     * @param $string
     * @return array[]|false|string[]
     */
    static function letterByUpperSplit($string)
    {
        return preg_split("/(?=[A-Z])/", $string);
    }

    /**
     * desc：将下划线命名 转换为 驼峰式命名
     * author：wh
     * @param string $str
     * @param bool $ucfirst
     * @return mixed|string
     */
    static function convertUnderLine(string $str, $ucfirst = true)
    {
        $str = ucwords(str_replace('_', ' ', $str));
        $str = str_replace(' ', '', lcfirst($str));
        return $ucfirst ? ucfirst($str) : $str;
    }

    /**
     * desc：将驼峰式命名 转换为 下划线命名
     * author：wh
     * @param $str
     * @return string
     */
    static function convertLineUnder($str)
    {
        return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $str));
    }


    /**
     * desc：获取数字的英文单词
     * author：wh
     * @param null $key
     * @return array|mixed
     */
    static function getNumEn($key = null)
    {
        $num_en = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
        if (!is_null($key)) return $num_en[$key];
        return $num_en;
    }


    /**
     * @param $filePath //下载文件的路径
     * @param $fileName //下载文件的名称
     * @param int $readBuffer //分段下载 每次下载的字节数 默认1024bytes
     * @param array $allowExt //允许下载的文件类型
     * @return void
     */
    static function downloadFile($filePath, $fileName = '', $readBuffer = 1024, $allowExt = ['jpeg', 'jpg', 'peg', 'gif', 'zip', 'rar', 'apk'])
    {
        //检测下载文件是否存在 并且可读
        if (!is_file($filePath) && !is_readable($filePath)) {
            return false;
        }
        //检测文件类型是否允许下载
        $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        if (!in_array($ext, $allowExt)) {
            return false;
        }
        //设置头信息
        //声明浏览器输出的是字节流
        header('Content-Type: application/octet-stream');
        //声明浏览器返回大小是按字节进行计算
        header('Accept-Ranges:bytes');
        //告诉浏览器文件的总大小
        $fileSize = filesize($filePath);//坑 filesize 如果超过2G 低版本php会返回负数
        header('Content-Length:' . $fileSize); //注意是'Content-Length:' 非Accept-Length
        //声明下载文件的名称
        header('Content-Disposition:attachment;filename=' . ($fileName ? $fileName : basename($filePath)));//声明作为附件处理和下载后文件的名称
        //获取文件内容
        $handle = fopen($filePath, 'rb');//二进制文件用‘rb’模式读取
        while (!feof($handle)) { //循环到文件末尾 规定每次读取（向浏览器输出为$readBuffer设置的字节数）
            echo fread($handle, $readBuffer);
        }
        fclose($handle);//关闭文件句柄
        exit;
    }

    /**
     * 文件直接下载[支持本地文件和远程文件]
     *   sys_download_file('web服务器中的文件地址', 'test.jpg');
     *   sys_download_file('远程文件地址', 'test.jpg', true);
     *
     * @param string $path 文件地址：针对当前服务器环境的相对或绝对地址
     * @param string $name 下载后的文件名（包含扩展名）
     * @param boolean $isRemote 是否是远程文件（通过 url 无法获取文件扩展名的必传参数 name）
     * @param string $proxy 代理，适用于需要使用代理才能访问外网资源的情况
     * @return true|false 下载结果
     */
    static function downloadUrl($path, $name = null, $isRemote = false, $contentType = 'binary_system', $proxy = '')
    {
        $fileRelativePath = $path;
        $savedFileName = $name;
        if (!$savedFileName) {
            $file = pathinfo($path);
            if (!empty($file['extension'])) {
                $savedFileName = $file['basename'];
            } else {
                throw new \Exception("Extension get failed, parameter 'name' is required!");
            }
        }

        // 如果是远程文件，先下载到本地
        if ($isRemote) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $path);
            if ($proxy != '') {
                curl_setopt($ch, CURLOPT_PROXY, $proxy);
            }
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 50);
            $fileContent = curl_exec($ch);
            curl_close($ch);

            // 写入临时文件
            $fileRelativePath = tempnam(sys_get_temp_dir(), 'DL');
            $fp = @fopen($fileRelativePath, 'w+');
            fwrite($fp, $fileContent);
        }

        $contentTypeArr = [
            //二进制
            'binary_system' => 'application/octet-stream',
            //apk安装包
            'apk' => 'application/vnd.android.package-archive',
        ];
        // 执行下载
        if (is_file($fileRelativePath)) {
            //传输类型
            header('Content-Description: File Transfer');
            //Content-Type
            if (empty($contentTypeArr[$contentType])) {
                //默认二进制格式
                header('Content-Type: application/octet-stream');
            } else {
                //根据参数指定下载格式
                header('Content-Type: ' . $contentTypeArr[$contentType]);
            }
            //文件大小
            header('Content-Length:' . filesize($fileRelativePath));

            if (preg_match('/MSIE/', $_SERVER['HTTP_USER_AGENT'])) { // for IE
                header('Content-Disposition: attachment; filename="' . rawurlencode($savedFileName) . '"');
            } else {
                header('Content-Disposition: attachment; filename="' . $savedFileName . '"');
            }
            readfile($fileRelativePath);
            if ($isRemote) {
                unlink($fileRelativePath); // 删除下载远程文件时对应的临时文件
            }
            exit;
        } else {
            throw new \Exception('Invalid file: ' . $fileRelativePath);
        }

    }

    /**
     * @param $v
     * @return bool
     * @deprecated 迁移到Validate.php类中
     * desc：是否是url
     * author：wh
     */
    static function is_url($v)
    {
        $pattern = "#(http|https)://(.*\.)?.*\..*#i";
        return preg_match($pattern, $v) ? true : false;
    }

    /**
     * [本地文件]
     * desc：根据一个完整的url地址，获取资源文件大小
     * author：wh
     * @param $url [一个可访问的站内合法地址。eg： http://39.101.214.157/static/upload/d4e271103e4536db/1d9f980651cdbc35.mp4 ]
     * @param string $domain 域名结束不含 "/" 符号
     * @return bool|false|int 文件大小
     */
    static function getFileSize($url, $domain = '')
    {
        $str = str_replace($domain ? $domain : request()->domain(), '', $url);

        $root_path = explode('vendor',__DIR__)[0];
        $mac_url = $root_path . 'public' . $str;
        if (!is_file($mac_url)) return false;
        return filesize($mac_url);
    }

    /**
     * [本地文件]
     * desc：根据一个完整的url地址，获取资源文件大小
     * 注：适用于thinkPHP6
     * author：wh
     * @param $url [一个可访问的站内合法地址。eg： http://39.101.214.157/static/upload/d4e271103e4536db/1d9f980651cdbc35.mp4 ]
     * @param string $domain 域名结束不含 "/" 符号
     * @param string $prefix_version 框架版本号前缀 eg:thinkPHP5.0就填写5，thinkPHP5.1就填写5.1，thinkPHP6.0就填写6
     * @return bool|false|int 文件大小
     */
    static function toGetFileSize($url, $domain = '')
    {
        $str = str_replace($domain ? $domain : request()->domain(), '', $url);


        $root_path = explode('vendor',__DIR__)[0];
        $mac_url = $root_path . 'public' . $str;
        if (!is_file($mac_url)) return false;
        return filesize($mac_url);
    }


    /**
     * desc：组装$_FILES原始数据,便于业务逻辑操作
     * author：wh
     * @param $name
     * @return array
     */
    static function groupFile($name)
    {
        $files = $_FILES[$name];
        $tmp = [];
        for ($j = 0; $j < count($files['name']); $j++) {
            $t['name'] = $files['name'][$j];
            $t['type'] = $files['type'][$j];
            $t['tmp_name'] = $files['tmp_name'][$j];
            $t['error'] = $files['error'][$j];
            $t['size'] = $files['size'][$j];
            $tmp[] = $t;
        }
        return $tmp;
    }

    /**
     * [获取协议类型]
     *
     * @return string
     * @link
     * @example
     * @see
     */
    static function getHttpType()
    {
        return input('server.REQUEST_SCHEME') . '://' . input('server.SERVER_NAME');
    }

    /**
     * @param $str
     * @return bool
     * @deprecated 迁移到Validate.php类中
     * [是否全部大写]
     *
     * @example
     * @see
     * @link
     */
    static function isUpper($str)
    {

        return preg_match('/^[A-Z]+$/', $str) ? true : false;
    }

    /**
     * @param $str
     * @return bool
     * @deprecated 迁移到Validate.php类中
     * [是否全部小写]
     *
     * @example
     * @see
     * @link
     */
    static function isLower($str)
    {
        return preg_match('/^[a-z]+$/', $str) ? true : false;
    }

    /**
     * 将字符串分割为数组
     * @param string $str 字符串
     * @return array       分割得到的数组
     */
    static function mbStrSplit($str)
    {
        return preg_split('/(?<!^)(?!$)/u', $str);
    }

    /**
     * 记录日志到文本-日志文件可即时删除
     *
     * 注：本方法仅适用于thinkPHP5.0
     *
     * @param string $data 数据（可以是数组）
     *
     * @param string $dirname 指定目录. 默认runtime/log/年月/时_error.log, 位置：runtime/log/
     *
     * 【温馨提示】
     * 异常日志不填写变量$dirname，自动写到{当日}_error.log
     *
     */
    static function log_to_write_txt($data = 'test', $dirname = '')
    {
        $root_path = explode('vendor',__DIR__)[0];
        $runtime_path = $root_path.'runtime/';
        if ($dirname) {
            $filepath = $runtime_path . 'log/' . ($dirname).'/'.date('Ymd');//运行时日志
        }else{
            $filepath = $runtime_path . 'log/'.date('Ym');//运行时日志
        }

        if (!file_exists($filepath)) {
            mkdir($filepath, 0777, true);
        }
        if($dirname){
            $filepath .= '/log' . date('YmdH') . '.txt';
        }else{
            $filepath .= '/' . date('d') . '_error.log';
        }
        $ip = request()->ip();
        list($t1, $t2) = explode(' ', microtime());
        $write_time = date('Y-m-d H:i:s',$t2).' '.sprintf('%.0f',$t1*1000);

        $str = "\n" . $write_time . ' | '. $ip.' | ' . request()->baseUrl() . "\n";

        $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data;

        file_put_contents($filepath, $data, FILE_APPEND);
    }

    /**
     * desc：获取项目根目录
     *
     * * 巧妙兼容各个tp版本
     *
     * author：wh
     * @return mixed|string
     */
    static function get_root_path(){
        return explode('vendor',__DIR__)[0];
    }
    /**
     * desc：获取项目名称
     *
     * * 巧妙兼容各个tp版本
     *
     * author：wh
     * @return mixed|string
     */
    static function get_project_name(){
        $root_path = explode('vendor',__DIR__)[0];
        $root_path = str_replace('\\','/',$root_path);
        $exp_arr = explode('/',$root_path);
        return $exp_arr[count($exp_arr)-2];
    }

    /**
     * 记录日志到文本-日志文件可即时删除
     *
     * 注：本方法仅适用于thinkPHP5.1+
     *
     * @param string $data 数据
     *
     * @param string $dirname 指定目录. 默认result_log, 位置：runtime/log/
     *
     */
    static function log_to_write_text($data = 'test', $dirname = '')
    {
        if (!$dirname) {
            $dirname = 'result_log';//存储在runtime/log/下面
        }

        $root_path = explode('vendor',__DIR__)[0];
        $runtime_path = $root_path.'runtime/';

        if ($dirname) {
            $filepath = $runtime_path . 'log/' . ($dirname).'/'.date('Ymd');//运行时日志
        }else{
            $filepath = $runtime_path . 'log/'.date('Ym');//运行时日志
        }

        if (!file_exists($filepath)) {
            mkdir($filepath, 0777, true);
        }
        if($dirname){
            $filepath .= '/log' . date('YmdH') . '.txt';
        }else{
            $filepath .= '/' . date('d') . '_error.log';
        }
        $ip = request()->ip();
        list($t1, $t2) = explode(' ', microtime());
        $write_time = date('Y-m-d H:i:s',$t2).' '.sprintf('%.0f',$t1*1000);

        $str = "\n" . $write_time . ' | '. $ip.' | ' . request()->baseUrl() . "\n";

        $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data;

        file_put_contents($filepath, $data, FILE_APPEND);
    }

    /**
     * 记录数据库操作日志到文本-日志文件可即时删除
     *
     * 注：本方法仅适用于thinkPHP5.0
     *
     * @param string $data 数据（可以是数组）
     *
     * @param string $dirname 指定目录. 默认result_log, 位置：runtime/log/
     */
    static function log_to_operate_sql(string $data, string $dirname)
    {
        $runtime_path = RUNTIME_PATH;

        $filepath = $runtime_path . 'log/' . ('sql_'.$dirname).'/'.date('Ymd');//运行时日志
        if (!file_exists($filepath)) {
            mkdir($filepath, 0777, true);
        }

        $filepath .= '/log' . date('Ymd') . '.sql';
        $str = "\n -- " . date('Y-m-d H:i:s')."\n";

        $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data;

        $data .= ';';

        file_put_contents($filepath, $data, FILE_APPEND);
    }

    /**
     * 记录数据库操作日志到文本-日志文件可即时删除
     *
     * 注：本方法仅适用于thinkPHP5.1+
     *
     * @param string $data 数据
     *
     * @param string $dirname 指定目录. 默认result_log, 位置：runtime/log/
     *
     */
    static function log_to_operate_sqls(string $data, string $dirname)
    {

        $runtime_path = \think\facade\App::getRuntimePath();

        $filepath = $runtime_path . 'log/' . ('sql_'.$dirname).'/'.date('Ymd');//运行时日志
        if (!file_exists($filepath)) {
            mkdir($filepath, 0777, true);
        }
        $filepath .= '/log' . date('Ymd') . '.sql';
        $str = "\n -- " . date('Y-m-d H:i:s')."\n";

        $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data;

        $data .= ';';

        file_put_contents($filepath, $data, FILE_APPEND);
    }

    /**
     * desc：
     *
     * author：wh
     * @param array $info
     */
    static function log(array $info){
        request()->LogObj->write($info);
    }
    /**
     * desc：秒转换为n时n分n秒
     * author：wh
     * @param int $s 时间戳(秒)
     * @return string
     */
    static function secondtostr(int $s)
    {
        $h = 60 * 60;
        $m = 60;

        $exp_h = explode('.', $s / $h);

        $hour = $exp_h[0];//小时数

        $exp_m = explode('.', ($s - $hour * $h) / $m);//$exp_m[0];
        $minute = $exp_m[0];//分钟数

        $second = ($s - $hour * $h - $minute * $m);

        return ($hour ? $hour : 0) . '小时' . ($minute ? $minute : 0) . '分钟' . ($second ? $second : 0) . '秒';
    }

    /**
     * desc：加密
     * author：wh
     * @param string $str
     * @return string
     */
    static function _md5(string $str)
    {
        return md5(md5($str));
    }

    /**
     * desc：浏览器环境检测
     * author：wh
     * @return string
     */
    static function web_env_type()
    {
        if (preg_match('~micromessenger~i', request()->header('user-agent'))) {
            return 'wechat';
        } else if (preg_match('~alipay~i', request()->header('user-agent'))) {
            return 'alipay';
        }
        return 'normal';
    }

    /**
     * desc：判断设备类型
     * author：wh
     * @return string
     */
    static function get_device_type()
    {
        //全部变成小写字母
        $agent = strtolower($_SERVER['HTTP_USER_AGENT']);
        $type = 'other';
        //分别进行判断
        if(strpos($agent, 'iphone') || strpos($agent, 'ipad'))
        {
            $type = 'ios';
        }

        if(strpos($agent, 'android'))
        {
            $type = 'android';
        }
        return $type;
    }


    /**
     * desc：签名
     *
     * 【注意】：
     * 鉴权时，如果数据提交方只用了参数中的部分数据进行签名，那么在校验系统中，
     * 签名校验是不合法的。所以，请将你的所有参数进行签名，再传输。
     *
     * 签名规则：
     * 1：对键值对数组按照键升序排序
     * 2：连接成字符串$tmpStr（去掉空值的键值对、去掉数组和对象）
     * 3：有token则在字符串后连接token
     * 4：二次md5加密,eg: md5(md5($tmpStr))
     *
     * 提示：可按照实际业务需求，对数据处理后再加签
     *
     * author：wh
     * @param array $sign_params 签名参数
     * @param string $token 加密token
     * @return string
     */
    static function signature(array $sign_params, string $token='')
    {
        //对关联数组按照键升序排序
        ksort($sign_params);
        //连接成字符串
        $tmpStr = "";
        foreach ($sign_params as $k => $v) {
            if($v == ''){
                continue;
            }
            if(is_array($v) || is_object($v)){
                continue;
            }
            $tmpStr .= $k . $v;
        }
        if($token) $tmpStr.='token'.$token;
        return md5(md5($tmpStr));//加密
    }

    /**
     * desc：CNY 人民币元转换为分，单位元 -> 分
     * author：wh
     * @param $yuan
     * @return float|int
     */
    static function money_yuan_to_cent($yuan){
        return $yuan * 100;
    }

    /**
     * desc：CNY 人民币分转换为元，单位分 -> 元
     * author：wh
     * @param $cent
     * @return float|int
     */
    static function money_cent_to_yuan($cent){
        return $cent / 100;
    }


    /**
     * desc：创建订单号
     *
     * author：wh
     * @param string $pay_type
     * @return string 21位长度字符串
     */
    static function to_create_order_no(string $pay_type){
        if(empty($pay_type)){
            return '';
        }
        $prefix = substr($pay_type,0,2);
        $randstr = Tools::rand_str(6);
        $time = Tools::getMillisecond();
        return $prefix.$randstr.$time;
    }

    /**
     * desc：将毫秒时间戳转换为时间
     *
     * 将毫秒时间戳转换为日期格式
     *
     * author：wh
     * @param int $milli_time 毫秒时间戳
     * @param string $date_format 目标格式 默认：Y-m-d H:i:s
     * @return false|string
     */
    static function milli_time_to_date(int $milli_time,string $date_format = 'Y-m-d H:i:s'){
        return date($date_format, ceil($milli_time/1000));
    }


    /**
     * desc：按规则拆分（中文）字符串
     *
     * author：wh
     * @param string $pattern 规则（正则表达式）；eg1：（//u 表示拆分成单个字符） ；eg2：（/ /u 表示使用空格拆分字符串）； 你也可使用其它正则。
     * @param string $str 被拆分的字符串
     * @param int $limit 限制条件，-1 表示无限制；正数 表示最多返回 limit 个子串；
     * @param int $flags 拆分标记
     * @return array|false|string[]
     *
     * 主要函数功能说明preg_split：
    说明 array preg_split ( string $pattern, string $subject [, int $limit [, int $flags]] )
    返回一个数组，包含 subject 中沿着与 pattern 匹配的边界所分割的子串。
    如果指定了 limit，则最多返回 limit 个子串，如果 limit 是 -1，则意味着没有限制，可以用来继续指定可选参数 flags。
    flags 可以是下列标记的任意组合（用按位或运算符 | 组合）：
    PREG_SPLIT_NO_EMPTY
    如果设定了本标记，则 preg_split() 只返回非空的成分。
    PREG_SPLIT_DELIM_CAPTURE
    如果设定了本标记，定界符模式中的括号表达式也会被捕获并返回。本标记添加于 PHP 4.0.5。
    PREG_SPLIT_OFFSET_CAPTURE
    如果设定了本标记，对每个出现的匹配结果也同时返回其附属的字符串偏移量。注意这改变了返回的数组的值，使其中的每个单元也是一个数组，其中第一项为匹配字符串，第二项为其在 subject 中的偏移量。本标记自 PHP 4.3.0 起可用。
     *
     * 欢迎测试。
     */
    static function preg_split_str(string $str, string $pattern='//u', int $limit=-1, int $flags=PREG_SPLIT_NO_EMPTY){

        //默认情况下将字符串拆分为单个字符并存储在一维数组中
        //变化较多的可能是变量$pattern
        return preg_split($pattern, $str, $limit, $flags);
    }


    /**
     * desc：返回用户的注册时间和对应的登录时间
     *
     * 注意：【因为登录时间值取的是昨天，所以此功能要今日执行】
     *
     * 备注：昨日注册用户今日登录就是次日留存，7日留存表示8天前（不算今天）注册的用户昨天登录，依次类推
     *
     * 问：为什么不在每天23:59:59秒执行呢？
     * 答：因为不能保证1秒就能执行玩业务流程，而且网络请求也需要时间，一秒时间太少（除非你有100%把握）
     *
     * author：wh
     *
     * @param int $keep_type 留存查询标识，数字，2表示2日留存，3表示3日留存，依次类推
     *
     * @param string $date_time 从给定的日期开始计算次日到7日留存时间，格式为：2021-10-30，那么计算得到的结果就是22日注册用户的7日留存时间
     *
     * @return array 返回注册时间和登录日期格式，按需（转换）使用
     *
     * 此功能已在现有项目生产环境中稳定运行
     *
     * 案例：
     *  //初始化日期处理工具(对统计功能非常友好)
        $date = new Date();
        //因为是统计功能，所以这里设置为Y-m-d格式
        $date->date_format = 'Y-m-d';
        //统计25日7日留存数据
        $date_time = '2021-10-25';
        //这里+8天是对应的25日，如果不加则从：25日-8日(不算代码执行当日)=17日开始计算次日，3日，4日......留存时间
        $date_time = $date->addTime(8,'d',strtotime($date_time));
        //统计留存的业务逻辑方法
        $logic = (new KeepStatisticsLogic());
        $logic->doKeepData($date_time);
     *
     *
     * //统计留存的业务逻辑方法
        function doKeepData(string $date_time){
        $keep_statistics_log = 'keep_statistics_log';



        //region 2日留存 start
        $keep_type = 2;
        Tools::log_to_write_txt(['title'=>'es统计 2日留存, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type,],$keep_statistics_log);
        //注册数
        $reg_num = $this->getRegNum($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 2日留存, 出参：','reg_num'=>$reg_num,
        'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log);
        //查询留存数据
        Tools::log_to_write_txt(['title'=>'es统计 2日留存,查询留存数据, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        $buckets2 = $this->countUserKeepDataByKeepType($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 2日留存,查询留存数据, 出参：','buckets2'=>$buckets2,
        'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log);
        //保存留存数据
        KeepStatisticsModel::insertDataByChannel($keep_type,$buckets2,$reg_num,$date_time,'two');
        //endregion 2日留存 end

        //region 3日留存 start
        $keep_type = 3;
        Tools::log_to_write_txt(['title'=>'es统计 3日留存, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type,],$keep_statistics_log);
        //注册数
        $reg_num = $this->getRegNum($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 3日留存, 出参：','reg_num'=>$reg_num,
        'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log);
        //查询留存数据
        Tools::log_to_write_txt(['title'=>'es统计 3日留存,查询留存数据, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        $buckets3 = $this->countUserKeepDataByKeepType($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 3日留存,查询留存数据, 出参：','buckets3'=>$buckets3,
        'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log);
        //保存留存数据
        KeepStatisticsModel::insertDataByChannel($keep_type,$buckets3,$reg_num,$date_time,'three');
        //endregion 3日留存 end



        //region 4日留存 start
        $keep_type = 4;
        Tools::log_to_write_txt(['title'=>'es统计 4日留存, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        //注册数
        $reg_num = $this->getRegNum($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 4日留存, 出参：','reg_num'=>$reg_num,
        'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log);
        //查询留存数据
        Tools::log_to_write_txt(['title'=>'es统计 4日留存,查询留存数据, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        $buckets4 = $this->countUserKeepDataByKeepType($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 4日留存,查询留存数据, 出参：','buckets4'=>$buckets4,
        'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log);
        //保存留存数据
        KeepStatisticsModel::insertDataByChannel($keep_type,$buckets4,$reg_num,$date_time,'four');
        //endregion 4日留存 end



        //region 5日留存 start
        $keep_type = 5;
        Tools::log_to_write_txt(['title'=>'es统计 5日留存, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type,],$keep_statistics_log);
        //注册数
        $reg_num = $this->getRegNum($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 5日留存, 出参：','reg_num'=>$reg_num,
        'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log);
        //查询留存数据
        Tools::log_to_write_txt(['title'=>'es统计 5日留存,查询留存数据, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        $buckets5 = $this->countUserKeepDataByKeepType($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 5日留存,查询留存数据, 出参：','buckets5'=>$buckets5,
        'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log);
        //保存留存数据
        KeepStatisticsModel::insertDataByChannel($keep_type,$buckets5,$reg_num,$date_time,'five');
        //endregion 5日留存 end



        //region 6日留存 start
        $keep_type = 6;
        Tools::log_to_write_txt(['title'=>'es统计 6日留存, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type,],$keep_statistics_log);
        //注册数
        $reg_num = $this->getRegNum($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 6日留存, 出参：','reg_num'=>$reg_num,
        'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log);
        //查询留存数据
        Tools::log_to_write_txt(['title'=>'es统计 6日留存,查询留存数据, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        $buckets6 = $this->countUserKeepDataByKeepType($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 6日留存,查询留存数据, 出参：','buckets6'=>$buckets6,
        'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log);
        //保存留存数据
        KeepStatisticsModel::insertDataByChannel($keep_type,$buckets6,$reg_num,$date_time,'six');
        //endregion 6日留存 end



        //region 7日留存 start
        $keep_type = 7;
        Tools::log_to_write_txt(['title'=>'es统计 7日留存, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type,],$keep_statistics_log);
        //注册数
        $reg_num = $this->getRegNum($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 7日留存, 出参：','reg_num'=>$reg_num,
        'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log);
        //查询留存数据
        Tools::log_to_write_txt(['title'=>'es统计 7日留存,查询留存数据, 入参：','登录时间(往前推keep_type天，且不含执行当日):'=>$date_time,
        'keep_type'=>$keep_type],$keep_statistics_log);
        $buckets7 = $this->countUserKeepDataByKeepType($keep_type,$date_time);
        Tools::log_to_write_txt(['title'=>'es统计 7日留存,查询留存数据, 出参：','buckets7'=>$buckets7,
        'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log);
        //保存留存数据
        KeepStatisticsModel::insertDataByChannel($keep_type,$buckets7,$reg_num,$date_time,'seven');
        //endregion 7日留存 end
        }
     *
     *
     * 注：KeepStatisticsModel::insertDataByChannel方法由开发者自己根据统计结果完成
     *
     */
    function revertUserKeepDate(int $keep_type, string $date_time){
        $date = new Date();
        $date->date_format = 'Y-m-d';
        $login_time_start = $date->reduceTime(1,'d',strtotime($date_time));//基础时间-1天
        $time_arr = [
            7=>[
                'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)),
                'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59',
                'login_time_start'=>$date->reduceTime(1,'d',strtotime($login_time_start)),
                'login_time_end'=>$date->reduceTime(1,'d',strtotime($login_time_start)).' 23:59:59',
            ],
            6=>[
                'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)),
                'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59',
                'login_time_start'=>$date->reduceTime(2,'d',strtotime($login_time_start)),
                'login_time_end'=>$date->reduceTime(2,'d',strtotime($login_time_start)).' 23:59:59',
            ],
            5=>[
                'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)),
                'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59',
                'login_time_start'=>$date->reduceTime(3,'d',strtotime($login_time_start)),
                'login_time_end'=>$date->reduceTime(3,'d',strtotime($login_time_start)).' 23:59:59',
            ],
            4=>[
                'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)),
                'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59',
                'login_time_start'=>$date->reduceTime(4,'d',strtotime($login_time_start)),
                'login_time_end'=>$date->reduceTime(4,'d',strtotime($login_time_start)).' 23:59:59',
            ],
            //3日留存
            3=>[
                'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)),
                'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59',
                'login_time_start'=>$date->reduceTime(5,'d',strtotime($login_time_start)),
                'login_time_end'=>$date->reduceTime(5,'d',strtotime($login_time_start)).' 23:59:59',
            ],
            //次日留存
            2=>[
                //注册时间
                'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)),
                'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59',
                //登录时间
                'login_time_start'=>$date->reduceTime(6,'d',strtotime($login_time_start)),
                'login_time_end'=>$date->reduceTime(6,'d',strtotime($login_time_start)).' 23:59:59',
            ],
        ];
        return $time_arr[$keep_type];
    }

    /**
     * desc：截取开始字符串（第一次出现），和结束字符串（第一次出现）之间的字符串
     *
     * 可以是html标签，也可以是中英文符号，如《，》，[，],{，}，===
     *
     * 也可以是任意字符串，由调用方指定
     *
     * 使用示例：
     * //取得json字符串
    $input_data_json = '{"users":"[{"uid":277997,"nickname":"小史姐姐"},{"uid":301005,"nickname":"???M?a?r?y???"},{"uid":302474,"nickname":"A汽车电工"},{"uid":296874,"nickname":"唐唐"}]","quota":5000,"entry_fee":100000,"match_type":4,"match_level":1}';

    //因为json解析不了多维数组，这里要将子json字符串获取出来
    $between_str = '['.$this->get_between_str($input_data_json,'[',']').']';
     *
     *
     * author：wh
     * @param $input
     * @param $start
     * @param $end
     * @return false|string
     */
    static function getBetweenStr($input, $start, $end){
        return substr($input, strlen($start) + strpos($input, $start), (strlen($input) - strpos($input, $end)) * (-1));
    }

    /**
     * desc：将一维数组的值转换为json字符串格式
     *
     * 备注：不含键名
     *
     * 使用频率低
     *
     * author：wh
     * @param array $arr eg: ['1234','5678','9991','3332'] 数组
     * @return string eg：['1234','5678','9991','3332'] 字符串
     */
    static function arrValToJsonStr(array $arr){
        $tmp_str = '[';
        foreach ($arr as $v){
            $tmp_str.=$tmp_str=='['?$v:','.$v;
        }
        $tmp_str.=']';
        return $tmp_str;
    }

    /**
     * @deprecated 废弃，效率低下
     *
     * desc：保留N位小数，并且向下取整
     *
     * author：wh
     * @param int $num
     * @return float|int
     */
    static function floorNum($num, $decimals=2){
        $strpos = strpos($num,'.');
        if(false !== $strpos){
            //存在
            $str = substr($num,0,$strpos);

            return $str.'.'.substr($num,$strpos+1,$decimals);
        }
        return 1*$num;
    }

    /**
     * desc：保留N位小数，并且向下取整
     *
     * 效率更高
     *
     * author：wh
     * @param $num
     * @param int $decimals
     * @return float|int
     */
    static function floor_num($num, $decimals=2){
        $dec = 1;
        $dec = str_pad($dec,($decimals+1),"0",STR_PAD_RIGHT);
        return floor($num*(int)$dec)/(int)$dec;
    }

    /**
     * desc：返回字符串格式的html结果，显示在浏览器中
     * author：wh
     * @param string $strData 要显示的字符串
     * @param false $load 是否跳转页面
     * @return string
     */
    static function returnHtml(string $strData='', $load=false){
        $req_str = '';
        if($load){
            $req_str = <<<EOF
style="display:none"  
onload='JavaScript:location.href="$strData";'
EOF;
            $strData = '';//置空
        }

        $html = <<<EOF
<html lang="zh"><head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
    </head>

    <body $req_str>
        <div style="
    color: gainsboro;
    margin: 0 auto;
    margin-top: 100px;
    text-align: center;
    width: 80%;
">
            $strData
  </div>
</body></html>
EOF;
        return $html;
    }


    /**
     * desc：获取当前url(模块/控制器/操作)【小写】
     *
     * author：wh
     * @return string
     */
    static function getRequestUrl(){
        return strtolower(request()->module().'/'.request()->controller().'/'.request()->action());
    }

    /**
     * desc：域名满足条件打印日志
     * author：wh
     * @return mixed
     */
    static function dump_log_in_domain($domain,$data){
        if(is_string($domain) && in_array(request()->host(),[$domain])){
            Tools::log_to_write_txt(['打印日志'=>$data],'aaaa');
        }else if(is_array($domain) && in_array(request()->host(),$domain)){
            Tools::log_to_write_txt(['打印日志'=>$data],'aaaa');
        }
    }


    /**
     * desc：jsonp的echo输出
     *
     * author：wh
     * @param string $callback js回调方法名称
     * @param array $result_data_arr 返回的数组
     */
    static function jsonpReturn(string $callback, array $result_data_arr){

        echo $callback.'('.json_encode($result_data_arr).')';die;//方法1
    }
    /**
     * desc：jsonp的return输出
     *
     * author：wh
     * @param string $callback js回调方法名称
     * @param array $result_data_arr 返回的数组
     */
    static function jsonp_return(string $callback, array $result_data_arr){

        return $callback.'('.json_encode($result_data_arr).')';//方法2
    }

    /**
     * desc：判断付款码来源
     *
     * 微信支付码规则：18位纯数字，以10、11、12、13、14、15开头
     *
     * 支付宝支付码规则：25 - 30开头的长度为16~24位的数字，实际字符串长度以开发者获取的付款码长度为准
     *
     *
     * author：wh
     */
    static function payment_code_origin($auth_code){
        // $auth_code 为授权码
        $payType = substr($auth_code, 0, 2);
        if ($payType < 25) {
            // 微信支付
            return 'wechat';
        }
        if ($payType > 50) {
            // 银行卡支付
            return 'bank';
        }
        // 支付宝支付
        return 'alipay';
    }

    /**
     * desc：已知期望毛利率，计算售价
     *
     * 售价计算器
     *
     * author：wh
     * @param $cost_price 成本
     * @param $hope_mao_lilv 期望毛利率
     * @return string
     */
    static function goto_count_sale_price($cost_price,$hope_mao_lilv){

        //毛利率=（售价-成本）/售价


        //循环，假设售价为N，毛利高于期望毛利，则N减1
        //$cost_price = input('cost_price');//成本
        //$hope_mao_lilv = input('hope_mao_lilv');//期望利率

        //$hope_mao_lilv = (N-$cost_price)
        $num = 1 - $hope_mao_lilv;//毛成本率
        //    总结：毛成本/毛成本率=售价
        $n = $cost_price/$num;
        return sprintf('%.2f',$n);
    }

    /**
     * desc：毛利率计算
     *
     * 计算毛利率
     *
     * author：wh
     * @param $sale_price 售价
     * @param $cost_price 成本
     */
    static function goto_count_mao_li_lv($sale_price,$cost_price){
        //毛利率=（售价-成本）/售价

        //$sale_price = input('sale_price');
        //$cost_price = input('cost_price');

        $mao = ($sale_price - $cost_price)/$sale_price;


        return sprintf('%.3f',$mao);
    }

    /**
     * 生成随机2位小数
     *
     * 默认小数后2位数
     * author：wh
     * @param $min 最小值
     * @param $max 最大值
     * @param int $sit 保留小数后几位
     * @return float
     * @throws \Exception
     */
    static function rand_float($min, $max, $sit=2)
    {
        if ($min >= $max) {
            throw new \Exception('最大值必须大于最小值.', 501);
        }
        $rand = $min + mt_rand() / mt_getrandmax() * ($max - $min);
        return floatval(sprintf("%.{$sit}f", $rand));
    }

    /**
     * desc：删除字符串中的空值部分
     *
     * author：wh
     * @param  string $str eg：,url-fa_attachment,,use-fa_auth_group,,,,,,,
     * @param string $sign 分隔符，将字符串拆分为数组
     * @return false|string[]
     */
    static function delete_str_empty_ele($str,$sign=','){
        $arr = explode($sign,$str);
        return array_filter($arr, function($value) {
            return $value !== "";
        });
    }

    /**
     * desc：删除数组中的"空字符串"元素
     *
     * 仅针对一维数组
     *
     * author：wh
     * @param array $arr eg:array("hello", "world", "", "php", "", "is", "cool")
     * @return array
     */
    static function delete_arr_empty_str(array $arr){
        return array_filter($arr, function($value) {
            return $value !== "";
        });
    }


    /**
     * desc：获取数据库所有的表
     *
     * author：wh
     * @return array
     */
    static function get_tables(){
        $tab = Db::query('SELECT DATABASE() as name');
        return array_column(Db::query('SHOW TABLES;'), 'Tables_in_'.$tab[0]['name']);
    }

    /**
     * desc：查询当前数据库名
     * author：wh
     * @return mixed
     */
    static function get_now_db_name(){
        $sql = "SELECT DATABASE() AS dbname;";
        return Db::query($sql)['dbname'];
    }

    /**
     * desc：获取表的注释
     *
     * author：wh
     * @param $tablename
     * @return mixed
     */
    static function get_table_comments($tablename){
        $sql = "SELECT DATABASE() AS dbname;";
        $dbname = Db::query($sql)[0]['dbname'];
        $sql = "SELECT TABLE_NAME tablename, TABLE_COMMENT comments
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = '{$dbname}' AND TABLE_NAME = '{$tablename}';";

        return Db::query($sql)[0]['comments'];
    }

    /**
     * desc：获取表所有字段的注释
     * author：wh
     */
    static function get_comments_by_field($tablename){
        $sql = "SELECT DATABASE() AS dbname;";
        $dbname = Db::query($sql)[0]['dbname'];
        $sql = "SELECT COLUMN_NAME column_name,COLUMN_COMMENT column_comment,DATA_TYPE data_type
FROM information_schema.columns WHERE TABLE_NAME='{$tablename}' AND table_schema='{$dbname}'";
        return Db::query($sql);
    }


    /**
     * desc：获取表字段注释
     * author：wh
     * @param $tablename
     * @param $field
     * @return mixed
     */
    static function get_comment_By_Field($tablename,$field){
        $field_comments = Tools::get_comments_by_field($tablename);
        $arr = Tools::key_val_arr($field_comments,'column_name','column_comment');
        return empty($arr[$field])?'':$arr[$field];
    }

    /**
     * desc：获取表中所有字段的数据类型
     *
     * author：wh
     */
    static function get_data_type_by_field($tablename){
        $sql = "SELECT DATABASE() AS dbname;";
        $dbname = Db::query($sql)[0]['dbname'];
        $sql = "SELECT COLUMN_NAME column_name,COLUMN_COMMENT column_comment,DATA_TYPE data_type
FROM information_schema.columns WHERE TABLE_NAME='{$tablename}' AND table_schema='{$dbname}'";
        $arr = Db::query($sql);
        $arr = Tools::key_val_arr($arr,'column_name','data_type');
        return $arr;
    }

    /**
     * desc：
     * author：wh
     * @param $e
     */
    static function error_txt_log($e){
        Tools::log_to_write_txt([
            'error'=>'系统错误.'.$e->getMessage(),
            'input'=>input(),
            'error_info'=>$e->getTraceAsString()
        ]);
    }


    /**
     * desc：n时间以前
     * 例如：1分钟以前，20分钟以前，3小时以前，1天以前，3天以前，18天以前，1个月以前
     *
     * $date 2021-02-13 12:33:43
     * author：wh
     */
    static function n_time_before($date){
        if(empty($date)){
            return '';
        }
        $time = time()-strtotime($date);
        //一个月默认30天
        $day_time = 86400;
        //月
        if($time >= 30*$day_time){
            return floor($time/30*$day_time).'月以前';
        }
        //半个月
        if($time >= 15*$day_time){
            return '半个月以前';
        }
        //天
        if($time >= $day_time){
            return floor($time/$day_time).'天以前';
        }
        $hour_time = 3600;
        //小时
        if($time >= $hour_time){
            return floor($time/$hour_time).'小时以前';
        }
        $min_hour = 60;
        //分钟
        if($time >= $min_hour){
            return floor($time/$min_hour).'分钟以前';
        }
        //秒
        return (floor($time/$day_time)?:1).'秒以前';
    }


    /**
     * desc：金钱保留2位小数
     *
     * 四舍五入
     *
     * author：wh
     * @param int $money 金额
     * @return string 统一返回2位数格式
     */
    static function money_float_two($money=0){
        return sprintf('%.2f',$money);
    }

    /**
     * desc：获取框架版本号，返回5.0, 5.1, 6.0, 6.1等
     * author：wh
     * @return float|int
     * @throws Exception
     */
    static function get_thinkphp_version(){
        $str = '';
        try {
            $str = \think\App::VERSION;
        }catch (\Exception $e){
            try {
                $str = THINK_VERSION;
            }catch (\Exception $e){
                try {
                    $str = \think\facade\App::version();
                }catch (\Exception $e){
                    throw new Exception('未获取到框架版本号');
                }
            }
        }
        $ver = substr($str,0,3);
        return $ver;
    }
}